#include "sudoku.h"
#include <time.h>
#include <curses.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>

#define PAIR_NORM 1
#define PAIR_USER 2
#define PAIR_USER_ERR 3
#define PAIR_HINT 4
#define PAIR_HINT_ERR 5
#define PAIR_BORDER 6

#define ATTR_NORM COLOR_PAIR(PAIR_NORM)
#define ATTR_USER COLOR_PAIR(PAIR_USER)
#define ATTR_USER_ERR COLOR_PAIR(PAIR_USER_ERR)
#define ATTR_HINT COLOR_PAIR(PAIR_HINT)
#define ATTR_HINT_ERR COLOR_PAIR(PAIR_HINT_ERR)
#define ATTR_BORDER COLOR_PAIR(PAIR_BORDER)

enum game_state {
	STATE_INPUT,
	STATE_COMMAND,
};

WINDOW* screen = NULL;
int curx = 2, cury = 1;
int sudx = 0, sudy = 0;
int scrw = 0, scrh = 0;
int state = STATE_INPUT;
int bad_number_hint = TRUE;

void end_game()
{
	endwin();
}

static void my_puts(const char* s)
{
	for(; *s != 0; ++s){
		addch(*s);
	}
}
static int my_geti(int def)
{
	char str[5] = {0,0,0,0,0}, *pStr = str;
	int ch = 0, v = 0;
	while(1){
		ch = getch();
		if( ch == '\n' ){ addch('\n'); break; };
		if( (unsigned long long)pStr < (unsigned long long)str || !isdigit(ch) ) continue;
		addch(ch);
		*(pStr++) = (char)ch;
	}
	if( pStr == str ) return def;
	sscanf(str, "%i", &v);
	return v;
}
static size_t my_gets(char* buffer, size_t maxlen)
{
	int ch = 0;
	char* startingBuffer = buffer;
	while(1){
		ch = getch();
		if( ch == '\n'){
			addch('\n');
			break;
		}
		if( maxlen > 1 ){ // leave one character for the null character
			*(buffer++) = (char)ch;
			--maxlen;
		}
		addch(ch);
	}
	*(buffer) = 0;
	return (buffer-startingBuffer);
}
		
		

/* draw the sudoku board to the screen */
void draw_sudoku(struct sudoku* sudoku, struct sudoku* original)
{
//	const char* top_row = "\311\315\315\315\313\315\315\315\313\315\315\315\313\315\315\315\313\315\315\315\313\315\315\315\313\315\315\315\313\315\315\315\313\315\315\315\313\n";
	move(0,0);
	attron(ATTR_BORDER);
	for(int x = 0; x < 9; ++x){
		addch('+');
		addch('-');
		addch('-');
		addch('-');
	}
	addch('+');
	addch('\n');
	for(int y = 0; y < 9; ++y){
		attron(ATTR_BORDER);
		addch('|');
		addch(' ');
		attron(ATTR_NORM);
		for(int x = 0; x < 9; ++x){
			if( sudoku->grid[y][x] != 0 && sudoku_bad_square(sudoku, x, y) && bad_number_hint ){
				if( original->grid[y][x] ) attron(ATTR_HINT_ERR);
				else attron(ATTR_USER_ERR);
			} else if(sudoku->grid[y][x] == original->grid[y][x] && sudoku->grid[y][x]) attron(ATTR_HINT);
			else if( sudoku->grid[y][x] == 0 ) attron(ATTR_NORM);
			else attron(ATTR_USER);
			if( sudoku->grid[y][x] ) addch('0'+sudoku->grid[y][x]);
			else addch(' ');
			attron(ATTR_NORM);
			addch(' ');
			if( (x%3) == 2 ) attron(ATTR_BORDER); // addch('|');
			else attron(ATTR_NORM);
			addch('|');
			addch(' ');
		}
		addch('\n');
		for(int x = 0; x < 9; ++x){
			if( (y%3) == 2 ){
				attron(ATTR_BORDER); // my_puts("+---");
				my_puts("+---");
			}else{
				if( (x%3) == 0 ) attron(ATTR_BORDER);//my_puts("+---");// my_puts("+---");
				else attron(ATTR_NORM);
				addch('+');
				attron(ATTR_NORM);
				my_puts("---");
			}
			attron(ATTR_NORM);
		/*	addch('+');
			addch('-');
			addch('-');
			addch('-');*/
		}
		attron(ATTR_BORDER);
		addch('+');
		addch('\n');
	}
	move(cury, curx);
	refresh();
}

void curses_init()
{
	/* initialize curses interface */
	screen = initscr();
	cbreak();
	noecho();
	getmaxyx(screen, scrw, scrh);
	keypad(screen, TRUE); // enable the arrows
	start_color();
	init_pair(PAIR_HINT, COLOR_WHITE, COLOR_BLACK);
	init_pair(PAIR_NORM, COLOR_WHITE, COLOR_BLACK);
	init_pair(PAIR_USER, COLOR_GREEN, COLOR_BLACK);
	init_pair(PAIR_USER_ERR, COLOR_BLACK, COLOR_RED);
	init_pair(PAIR_HINT_ERR, COLOR_WHITE, COLOR_BLACK);
	init_pair(PAIR_BORDER, COLOR_CYAN, COLOR_BLACK);
}

int main(int argc, char** argv)
{
	/* some randomization fun.
	 * first we seed the randomizer with the current system time, then
	 * we recalculate the seed by adding a random number to the old
	 * seed and multiplying by the current time.
	 */
	{
		long long seed = time(NULL);
		srand(seed);
		seed = (seed + rand())*(seed + time(NULL));
		srand(seed);
	}
	
	curses_init();
	ESCDELAY=25;
	if( has_colors() == FALSE ){
		clear();
		attron(COLOR_PAIR(1));
		my_puts("note: your terminal does not support colored text.\n\tthe hint values will be indistinguishable from the values you enter.\n");
		my_puts("press any key to continue...\n");
		refresh();
		getch();
	}
	
	/* local variables */
	struct sudoku sudoku, original;
	int ch = 0, need_refresh = 1;
	
	/* initialize the board */
	sudoku_generate(&original, 35);
	sudoku = original;
	
	atexit(end_game);
	
	while( 1 )
	{
		if( need_refresh ){
			clear();
			draw_sudoku(&sudoku, &original);
			move(cury, curx);
			if( state == STATE_COMMAND ){
				curx = 0; cury = 1+2*9;
				move(1+2*9, 0);
				addch(':');
			}
			refresh();
			need_refresh = 0;
		}

		ch = getch();
		if( state == STATE_INPUT )
		{
			switch(ch)
			{
				case KEY_LEFT: sudx--; curx -= 4; need_refresh = 1; break;
				case KEY_RIGHT: sudx++; curx += 4; need_refresh = 1; break;
				case KEY_UP: sudy--; cury -= 2; need_refresh = 1; break;
				case KEY_DOWN: sudy++; cury+= 2; need_refresh = 1; break;
				case 27: state = STATE_COMMAND; need_refresh = 1; break;
				case KEY_BACKSPACE:
					if( original.grid[sudy][sudx] != 0 ) break;
					sudoku.grid[sudy][sudx] = 0;
					need_refresh = 1;
					break;
				default:
					if( isdigit(ch) && ch != '0' && (original.grid[sudy][sudx] == 0) ){
						sudoku.grid[sudy][sudx] = ch-'0';
						need_refresh = 1;
					}
			}
			
			if( sudx < 0 ){ sudx = 0; curx = 2; }
			if( sudy < 0 ){ sudy = 0; cury = 1; }
			if( sudx > 8 ){ sudx = 8; curx = 2+4*sudx; }
			if( sudy > 8 ){ sudy = 8; cury = 1+2*sudy; }

			move(cury, curx);
		} else if( state == STATE_COMMAND ){
			attron(ATTR_NORM);
			int reply, r;
			char file_name[128] = {0};
			switch(ch)
			{
				case 'b':
					bad_number_hint = !bad_number_hint;
					break;
				case 'q':
				case 'e':
					my_puts("Are you want to quit? (y/N):");
					refresh();
					while( (reply = tolower(getch())) != 'y' && reply != 'n' && reply != '\n' );
					if( reply == 'y' ) exit(0);
					break;
				case 'r':
					my_puts("Are you sure you want to reset the board? (y/N):");
					refresh();
					while( (reply = tolower(getch())) != 'y' && reply != 'n' && reply != '\n' );
					if( reply == 'y' ) memcpy( &sudoku, &original, sizeof(struct sudoku) );
					break;
				case 's':
					my_puts("Are you sure you want me to finish solving the board? (y/N):");
					refresh();
					while( (reply = tolower(getch())) != 'y' && reply != 'n' && reply != '\n' );
					if( reply == 'y' ){
						if( !sudoku_solve(&sudoku) ){
							my_puts("\nThe puzzle is unsolvable with it's current contents! Use the reset command, then try and solve it again.\n");
							my_puts("press any key to continue...\n");
							getch();
						}
					}
					break;
				case 'c':
				{
					struct sudoku test; memcpy(&test, &original, sizeof(struct sudoku));
					sudoku_solve(&test);
					if( memcmp(&test, &sudoku, sizeof(struct sudoku)) == 0 ){
						my_puts("You solved the board! Good job!\n");
					} else {
						my_puts("Sorry, you are not done.\n");
					}
					my_puts("Press any key to continue...");
					getch();
				}
				break;
				case 'n':
					my_puts("Are you sure you want load a new puzzle? (y/N):");
					refresh();
					while( (reply = tolower(getch())) != 'y' && reply != 'n' && reply != '\n' );
					if( reply == 'n' || reply == '\n' ){
						break;
					}
					int hints = 0;
					do{
						my_puts("\nEnter the number of hints between 1 and 80: [35] ");
						hints = my_geti(35);
					} while ( hints < 1 || hints > 80 );
					//printf("%d", hints);
					sudoku_generate(&original, hints);
					sudoku = original;
					break;
				case 'l':
					my_puts("Are you sure you want to load a new puzzle? (y/N):");
					refresh();
					while( (reply = tolower(getch())) != 'y' && reply != 'n' && reply != '\n' );
					if( reply == 'n' || reply == '\n' ){
						break;
					}
					my_puts("\nEnter the name of the save file:");
					refresh();
					my_gets(file_name, 128);
					r = sudoku_load(&sudoku, &original, file_name);
					if( r == -1 ){
						my_puts("Error: could not open file for reading.\n");
					}
					my_puts("press any key to continue...\n");
					getch();
					break;
				case 'S':
					my_puts("Enter the name of the save file:");
					refresh();
					my_gets(file_name, 128);
					r = sudoku_save(&sudoku, &original, file_name);
					if( r == -1 ){
						my_puts("Error: unable to open file for writing.\n");
					}
					my_puts("press any key to continue...\n");
					getch();
					break;
				case 'h':
				case '?':
					my_puts("help:\nn:\t\tLoad a new puzzle\nc:\t\tCheck the current puzzle for completeness.\ns:\t\tLet me solve the current puzzle.\nr:\t\tReset the puzzle to its original state.\nq/e:\t\texit the game.\n");
					my_puts("\npress any key to continue...");
					getch();
					break;
			}
			curx = 2+4*sudx;
			cury = 1+2*sudy;
			state = STATE_INPUT;
			need_refresh = 1;
		}
	}
	
	return 0;
}
